package com.mo.tools.db;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.kotlin.KtQueryChainWrapper;
import com.baomidou.mybatisplus.extension.kotlin.KtUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * description: MPDB
 * author: e-Xingfu.Yang
 * date: 2022/5/11 17:20
 */
@SuppressWarnings("all")
public class MPDB {

    private static Log log = LogFactory.getLog(MPDB.class);
    public static final Map<Type, Object> baseMapperMap = new LinkedHashMap<>(16);

    protected static int DEFAULT_BATCH_SIZE = 1000;

    public static <T extends Object> boolean save(T entity) {
        return SqlHelper.retBool(((BaseMapper<T>) getMapper(entity.getClass())).insert(entity));
    }


    public static <T> boolean saveOrUpdate(T entity) {
        if (null != entity) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            Object idVal = ReflectionKit.getFieldValue(entity, tableInfo.getKeyProperty());
            return StringUtils.checkValNull(idVal) || Objects.isNull(getById(entity.getClass(), (Serializable) idVal)) ? save(entity) : updateById(entity);
        }
        return false;
    }

    public static <T> boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
        return update(entity, updateWrapper) || saveOrUpdate(entity);
    }


    public static <T> boolean saveBatch(Collection<T> entityList) {
        if (ObjectUtils.isEmpty(entityList)) {
            return false;
        } else {
            Class<?> entityClass = entityList.stream().findFirst().get().getClass();
            String sqlStatement = getSqlStatement(entityClass, SqlMethod.INSERT_ONE);
            return executeBatch(entityList, DEFAULT_BATCH_SIZE, (sqlSession, entity) -> {
                sqlSession.insert(sqlStatement, entity);
            });
        }
    }

    public static <T> boolean saveOrUpdateBatch(Collection<T> entityList) {
        if (ObjectUtils.isEmpty(entityList)) {
            return false;
        } else {
            Class<?> entityClass = entityList.stream().findFirst().get().getClass();
            TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            return SqlHelper.saveOrUpdateBatch(entityClass, getMapperClass(entityClass), log, entityList, DEFAULT_BATCH_SIZE, (sqlSession, entity) -> {
                Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
                return StringUtils.checkValNull(idVal)
                        || CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(entityClass, SqlMethod.SELECT_BY_ID), entity));
            }, (sqlSession, entity) -> {
                MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
                param.put(Constants.ENTITY, entity);
                sqlSession.update(getSqlStatement(entityClass, SqlMethod.UPDATE_BY_ID), param);
            });
        }
    }

    public static <T> boolean removeById(Class<T> entityClass, Serializable id) {
        return SqlHelper.retBool(((BaseMapper<T>) getMapper(entityClass)).deleteById(id));
    }

    public static <T> boolean removeByMap(Class<T> entityClass, Map<String, Object> columnMap) {
        Assert.notEmpty(columnMap, "error: columnMap must not be empty", new Object[0]);
        return SqlHelper.retBool(((BaseMapper<T>) getMapper(entityClass)).deleteByMap(columnMap));
    }

    public static <T> boolean remove(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return SqlHelper.retBool(((BaseMapper<T>) getMapper(entityClass)).delete(queryWrapper));
    }

    public static <T> boolean removeByIds(Class<T> entityClass, Collection<? extends Serializable> idList) {
        return CollectionUtils.isEmpty(idList) ? false : SqlHelper.retBool(((BaseMapper<T>) getMapper(entityClass)).deleteBatchIds(idList));
    }

    public static <T> boolean updateById(T entity) {
        return SqlHelper.retBool(((BaseMapper<T>) getMapper(entity.getClass())).updateById(entity));
    }

    public static <T> boolean update(T entity, Wrapper<T> updateWrapper) {
        return SqlHelper.retBool(((BaseMapper<T>) getMapper(entity.getClass())).update(entity, updateWrapper));
    }

    public static <T> boolean updateBatchById(Collection<T> entityList) {
        if (ObjectUtils.isEmpty(entityList)) {
            return false;
        } else {
            Class<?> entityClass = entityList.stream().findFirst().get().getClass();
            String sqlStatement = getSqlStatement(entityClass, SqlMethod.UPDATE_BY_ID);
            return executeBatch(entityList, DEFAULT_BATCH_SIZE, (sqlSession, entity) -> {
                MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap();
                param.put("et", entity);
                sqlSession.update(sqlStatement, param);
            });
        }
    }


    public static <T> T getById(Class<T> entityClass, Serializable id) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectById(id);
    }

    public static <T> T getOne(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return getOne(entityClass, queryWrapper, false);
    }

    public static <T> T getOne(Class<T> entityClass, Wrapper<T> queryWrapper, boolean throwEx) {
        BaseMapper<T> baseMapper = (BaseMapper<T>) getMapper(entityClass);
        return throwEx ? baseMapper.selectOne(queryWrapper) : SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
    }

    public static <T> Map<String, Object> getMap(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return SqlHelper.getObject(log, ((BaseMapper<T>) getMapper(entityClass)).selectMaps(queryWrapper));
    }

    public static <T, V> V getObj(Class<T> entityClass, Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
        return SqlHelper.getObject(log, listObjs(entityClass, queryWrapper, mapper));
    }

    public static <T> List<T> listByIds(Class<T> entityClass, Collection<? extends Serializable> idList) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectBatchIds(idList);
    }

    public static <T> List<T> listByMap(Class<T> entityClass, Map<String, Object> columnMap) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectByMap(columnMap);
    }

    public static <T> List<T> list(Class<T> entityClass) {
        return list(entityClass, Wrappers.emptyWrapper());
    }

    public static <T> List<T> list(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectList(queryWrapper);
    }

    public static <T> List<Map<String, Object>> listMaps(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectMaps(queryWrapper);
    }

    public static <T> List<Map<String, Object>> listMaps(Class<T> entityClass) {
        return listMaps(entityClass, Wrappers.emptyWrapper());
    }

    public static <T> List<Object> listObjs(Class<T> entityClass) {
        return listObjs(entityClass, Function.identity());
    }

    public static <T, V> List<V> listObjs(Class<T> entityClass, Function<? super Object, V> mapper) {
        return listObjs(entityClass, Wrappers.emptyWrapper(), mapper);
    }

    public static <T> List<Object> listObjs(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return listObjs(entityClass, queryWrapper, Function.identity());
    }

    public static <T, V> List<V> listObjs(Class<T> entityClass, Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
        return (List) ((BaseMapper<T>) getMapper(entityClass)).selectObjs(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
    }

    public static <E extends IPage<T>, T> E page(Class<T> entityClass, E page, Wrapper<T> queryWrapper) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectPage(page, queryWrapper);
    }

    public static <E extends IPage<T>, T> E page(Class<T> entityClass, E page) {
        return page(entityClass, page, Wrappers.emptyWrapper());
    }

    public static <T, E extends IPage<Map<String, Object>>> E pageMaps(Class<T> entityClass, E page, Wrapper<T> queryWrapper) {
        return ((BaseMapper<T>) getMapper(entityClass)).selectMapsPage(page, queryWrapper);
    }

    public static <T, E extends IPage<Map<String, Object>>> E pageMaps(Class<T> entityClass, E page) {
        return pageMaps(entityClass, page, Wrappers.emptyWrapper());
    }

    public static <T> int count(Class<T> entityClass) {
        return count(entityClass, Wrappers.emptyWrapper());
    }

    public static <T> int count(Class<T> entityClass, Wrapper<T> queryWrapper) {
        return SqlHelper.retCount(((BaseMapper<T>) getMapper(entityClass)).selectCount(queryWrapper));
    }

    public static <T> QueryChainWrapper<T> chainQuery(Class<T> entityClass) {
        return ChainWrappers.queryChain(((BaseMapper<T>) getMapper(entityClass)));
    }


    public static <T> UpdateChainWrapper<T> chainUpdate(Class<T> entityClass) {
        return ChainWrappers.updateChain(((BaseMapper<T>) getMapper(entityClass)));
    }

    public static <T> LambdaQueryChainWrapper<T> lambdaQuery(Class<T> entityClass) {
        return ChainWrappers.lambdaQueryChain(((BaseMapper<T>) getMapper(entityClass)));
    }

    public static <T> KtQueryChainWrapper<T> ktQuery(Class<T> entityClass) {
        return ChainWrappers.ktQueryChain(((BaseMapper<T>) getMapper(entityClass)), entityClass);
    }

    public static <T> KtUpdateChainWrapper<T> ktUpdate(Class<T> entityClass) {
        return ChainWrappers.ktUpdateChain(((BaseMapper<T>) getMapper(entityClass)), entityClass);
    }


    public static <T> LambdaUpdateChainWrapper<T> lambdaUpdate(Class<T> entityClass) {
        return ChainWrappers.lambdaUpdateChain(((BaseMapper<T>) getMapper(entityClass)));
    }

    protected static String getSqlStatement(Class<?> entityClass, SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(getMapperClass(entityClass), sqlMethod);
    }

    /**
     * 执行批量操作
     *
     * @param list               数据集合
     * @param DEFAULT_BATCH_SIZE 批量大小
     * @param consumer           执行方法
     * @param <E>                泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected static <E> boolean executeBatch(Collection<E> list, int DEFAULT_BATCH_SIZE, BiConsumer<SqlSession, E> consumer) {
        if (ObjectUtils.isEmpty(list)) {
            return false;
        } else {
            Class<?> entityClass = list.stream().findFirst().get().getClass();
            return SqlHelper.executeBatch(entityClass, MPDB.log, list, DEFAULT_BATCH_SIZE, consumer);
        }
    }

    /**
     * 执行批量操作（默认批次提交数量{@link IService#DEFAULT_BATCH_SIZE}）
     *
     * @param list     数据集合
     * @param consumer 执行方法
     * @param <E>      泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected static <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
        return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
    }

    public static <T> BaseMapper<T> getMapper(Class<T> entityClass) {
        MapperFactoryBean<T> mapperFactoryBean = getMapperFactoryBean(entityClass);
        try {
            return (BaseMapper<T>) mapperFactoryBean.getObject();
        } catch (Exception e) {
            throw new RuntimeException("mapping mapper not exists".format(entityClass.getSimpleName()));
        }
    }

    private static <T> Class<T> getMapperClass(Class<T> entityClass) {
        MapperFactoryBean<T> mapperFactoryBean = getMapperFactoryBean(entityClass);
        try {
            return mapperFactoryBean.getMapperInterface();
        } catch (Exception e) {
            throw new RuntimeException("mapping mapper not exists".format(entityClass.getSimpleName()));
        }
    }

    private static <T> MapperFactoryBean<T> getMapperFactoryBean(Class<T> entityClass) {
        if (entityClass == null) {
            throw new RuntimeException("table class type is empty");
        }
        MapperFactoryBean mapperFactoryBean = (MapperFactoryBean) baseMapperMap.get(entityClass);
        if (mapperFactoryBean == null) {
            throw new RuntimeException("mapping mapper not exists".format(entityClass.getSimpleName()));
        }
        return mapperFactoryBean;
    }


    @Component
    private static class DbBeanPostProcessor implements BeanPostProcessor {


        /**
         * register mapper to MPDB
         *
         * @param bean
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            if (bean instanceof MapperFactoryBean) {
                MapperFactoryBean mapperFactoryBean = (MapperFactoryBean) bean;
                try {
                    Object object = mapperFactoryBean.getObject();
                    Class mapperInterface = mapperFactoryBean.getMapperInterface();
                    Type genericInterface = mapperInterface.getGenericInterfaces()[0];
                    ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
                    Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
                    baseMapperMap.put(actualTypeArgument, bean);
                } catch (Exception e) {
                    throw new RuntimeException("%beanName mapping mapper not exists");
                }
            }

            return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
        }
    }

}
